EDA von Gebrauchtwagenangeboten der Website “Autoscout24”

Von Annika Scheug und Oliver Schabe, Vorlesung “Python for Data Science”, Sommersemester 2022

Einleitung

In diesem Projekt wird eine Explorative Datenanalyse von Gebrauchtwagenangeboten der Website “Autoscout24” durchgefuehrt.
Autoscout24 (https://www.autoscout24.de/) ist eine Online-Plattform zum Kauf und Verkauf von Neu- und Gebrauchtwagen.
Die Startseite von Autoscout24 sieht folgendermaßen aus:
Autoscout24 Startseite

Die Suchergebnisse werden in einer Liste dargestellt, wobei je Suche immer maximal 20 Seiten mit je 20 Fahrzeugen ausgegeben werden:

Autoscout24 Suche

Ziel des Projektes ist es, Daten über Zustand und Ausstattung verschiedener Fahrzeuge aus dem Quellcode der Website abzuziehen und diese Daten anschließend ausführlich zu analysieren. Bei der Analyse der Daten sollen mögliche Korrelationen zwischen den Fahrzeugdaten untersucht werden. Der Schwerpunkt soll dabei auf dem Verkaufspreis und den Fahrzeugeigenschaften liegen, die einen relevanten Einfluss auf den Verkaufspreis haben (wie möglicherweise Kilometerstand, Alter des Fahrzeuges etc.).
Ein möglicher praktischer Anwendungsfall der hier gesammelten Erkentnisse könnte beispielsweise ein Tool sein, welches interessierten Käufern oder Verkäufern realistische Preisvorschläge auf Basis von Angaben zum Fahrzeugszustand macht.
Die Entwicklung einer solchen Applikation ist jedoch nicht im Scope dieses Projektes.

Import

Zunächst werden alle für dieses Projekt benötigten Packages und Bibliotheken importiert.

#Basics
import pandas as pd
import numpy as np

#Webcrawling
#pip install beautifulsoup4
from bs4 import BeautifulSoup
import requests

#Deactivate warnings
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
#Data visualization
import matplotlib.pyplot as plt

import plotly.express as px
import plotly.graph_objs as go
import plotly.io as pio
import plotly.figure_factory as ff
import plotly.offline as pyo
# Set notebook mode to work in offline
pyo.init_notebook_mode()

import seaborn as sns 

np.set_printoptions(precision=6)
np.set_printoptions(suppress=True)
pd.set_option("display.max_columns", 200)
pd.set_option("display.max_rows", 200)
#Geo visualization
import folium
#!pip install geopy
from geopy.geocoders import Nominatim

#Postgre SQL
import psycopg2 
import json 
from sqlalchemy import create_engine 

#Regression
import statsmodels.formula.api as smf

Webcrawling & Erzeugung des Dataframes

Als Erstes ist es notwendig, die Fahrzeugdaten von der Webseite Autoscout24 zu crawlen und in einem Dataframe zu speichern.

Für das Crawlen der Daten wird die Methode extractPageCarDF definiert. Dieser muss beim Aufruf die Variable URL mitgegeben werden. Dabei handelt es sich um den Link zu einem Autoscout24 Suchergebnis, welches stets 20 Autos beinhaltet (sofern sie den Suchkriterien entsprechen).
Jedes Auto wird dabei vom HTML Element Article umschlossen. Daher wird eine Schleife implementiert, welche für jedes Auto im Suchergebnis die nachfolgenden Daten aus den dazugehörigen HTML Elementen der Webseite extrahiert:

  • Titel

  • Fahrzeugversion

  • Untertitel

  • Preis

  • Leasing oder Kaufen

  • Fahrzeugstandort

Falls eines der HTML-Elemente nicht gefunden werden kann, wird jede der Anweisungen durch einen try except Block umschlossen. Falls kein Eintrag gefunden wird, wird der jeweilige Wert mit einem NULL-Wert belegt.

Eine Besonderheit ist zudem der Preis. Wird das Element ListItem_pricerow gefunden, handelt es sich um einen Verkaufspreis und kein Leasingangebot. Leasing wird daher False gesetzt. Wird dieses Element nicht gefunden, sondern LeasingPrice_price, handelt es sich um ein Leasingangebot.

Diese Daten werden dem Dataframe pageCarDF hinzugefügt.

In einer weiteren Schleife werden die folgenden Fahrzeugdetaildaten aus dem HTML Div Container VehicleDetailTable abgezogen:
Autoscout24 VehicleDetailTable
Es wird zunächst ein leeres Dataframe VehicleDetailDF erstellt. In einer Schleife wird für jedes Fahrzeug die leere Liste VehicleDetailList erzeugt und dieser in einer inneren Schleife jedes Element der VehicleDetailTable hinzugefügt.
Die Liste wird anschließend dem VehicleDetailDF hinzugefügt. Hierfür ist ein try except notwendig. Leasing Fahrzeuge haben ein zweites Element namens VehicleDetailTable, welches allerdings nur 3 Einträge zum Themengebiet Leasing hat. Der Versuch diese Listen mit 3 Einträgen dem VehicleDetailDF hinzuzufügen, läuft aufgrund nicht passender Längen auf Fehler. Dieser Fehler wird im except Block bewusst mit einem Continue abgefangen. Die Leasing VehicleDetailLists werden nicht weiter benötigt und fallen somit raus.

Nun liegt das pageCarDF und das VehicleDetailDF vor, welche beide je Fahrzeug eine Zeile beinhalten.
Die beiden Dataframes werden mithilfe der merge-Methode über den Index gejoined.

Die Methode gibt das Dataframe pageCarDF als return value zurück. Dieses beinhaltet alle relevanten Daten von den Fahrzeugen einer Suchergebnisseite (in der Regel 20 Fahrzeuge).

def extractPageCarDF(URL):

    soup=BeautifulSoup(requests.get(URL).text,"html.parser")
    pageCarDF=pd.DataFrame()

    for car in soup.findAll("article"):
        data = car.find("div", {"class": lambda L: L and L.startswith("ListItem_wrapper")})
        try:
            header = data.find("h2").text
        except:
            header = np.NaN
        try:
            version = data.find("span", {"class": lambda L: L and L.startswith("ListItem_version")}).text
        except:
            version = np.NaN
        try:
            subtitle = data.find("span", {"class": lambda L: L and L.startswith("ListItem_subtitle")}).text
        except:
            subtitle = np.NaN
        try:
            #Versuch Preis Element zu finden
            price = data.find("div", {"class": lambda L: L and L.startswith("ListItem_pricerow")}).text
            leasing = False
        except:
            #wenn oberes Element nicht gefunden werden kann, handelt es sich um einen Leasing Wagen, mit dem nachfolgenden HTML Element
            price = data.find("span", {"class": lambda L: L and L.startswith("LeasingPrice_price")}).text
            leasing = True 

        try:
            location = car.find("span", {"style": lambda L: L and L.startswith("grid-area:address")}).text
        except:
            location = np.NaN

        #Daten dem pageCarDF hinzufügen
        pageCarDF = pageCarDF.append({"Titel":header, "Version":version, "Untertitel":subtitle, "Preis":price, "Leasing":leasing, "Standort":location}, ignore_index=True)


    #VehicleDetailTable
    VehicleDetailDF = pd.DataFrame()
    for car in soup.findAll("div" , {"class":"VehicleDetailTable_container__mUUbY"}):
        VehicleDetailList = []
        for c in car:
            VehicleDetailList.append(c.text)
        try:
            VehicleDetailDF = VehicleDetailDF.append({"km":VehicleDetailList[0], "Erstzulassung":VehicleDetailList[1], "PS":VehicleDetailList[2], "Zustand":VehicleDetailList[3], "Fahrzeughalter":VehicleDetailList[4], "Getriebe":VehicleDetailList[5], "Kraftstoff": VehicleDetailList[6], "Verbrauch_l_pro_100km":VehicleDetailList[7], "Emissionen_g_pro_km":VehicleDetailList[8]}, ignore_index=True)
        except:
            continue #VehicleDetailLists mit Länge 3 sind extra VehicleDetailTables, die nur bei Leasing Wagen vorkommen. Diese sollen nicht übernommen werden, daher Continue
    
    #Join pageCarDF und VehicleDetailDF
    pageCarDF = pd.merge(pageCarDF, VehicleDetailDF, left_index=True, right_index=True)   

    return pageCarDF

Die Methode extractPageCarDF gilt es nun mit den passenden Parametern aufzurufen.

Es werden zunächst zwei leere Dataframes initialisiert.
Die Suche auf Autoscout24 wurde zunächst komplett ohne Filter aufgerufen. Pro Suchergebnis gibt die Webseite ingesamt 20 Suchergebnisseiten mit jeweils 20 Fahrzeugen aus. Somit können mit einer Suche maximal 20 * 20 = 400 Autos von der Webseite gecrawlt werden.
Da für die Analysen im Projekt mehr als 400 Datensätze gewünscht sind, wird ein Filter “Erstzulassung von” (fregfrom) und “Erstzulassung bis” (fregto) gesetzt. Die Jahreszahlen werden in der Liste fregList von 1990 bis 2022 in 1 Jahresschritten gewählt.

In einer Schleife wird zunächst der Filter auf die jeweilige Jahreszahl gesetzt. In einer inneren Schleife wird jeweils die Ergebnisseite der Suche festgelegt. Somit werden pro Iteration der äußeren Schleife 20 Seiten des Suchergebnisses gecrawlt.
Dafür wird zunächst die URL aus “Erstzulassung von” fregfrom= , “Erstzulassung bis” fregto= und Suchergebnisseite page erstellt. Die URL wird an die Methode extractPageCarDF übergeben und diese ausgeführt.
Das resultierende Dataframe pageCarDF mit 20 Fahrzeugen wird dem Dataframe AutoDFraw angehängt. Anschließend wird die Methode für die nächste Seite im Suchergebnis ausgeführt und das Ergebnis wieder AutoDFraw hinzufügt.
Dataframe pageCarDF wird somit bei jeder Ausführung der Methode extractPageCarDF neu erstellt, während Dataframe AutoDFraw immer weiter wächst.

Die Methode wird für jeden Filter “Erstzulassung bis” für 20 Suchergebnisseiten ausgeführt, sodass das Dataframe AutoDFraw am Ende über 6000 Einträge enhält.

AutoDFraw=pd.DataFrame()
pageCarDF=pd.DataFrame()
baselink = "https://www.autoscout24.de/lst?fregfrom="
fregList = list(range(1990, 2022, 1))

for freg in fregList:
    for page in range(20):
        URL = baselink + str(freg) + "&fregto=" + str(freg) + "&page=" + str(page)
        pageCarDF = extractPageCarDF(URL)
        AutoDFraw=pd.concat([AutoDFraw, pageCarDF],axis=0, ignore_index=True)
AutoDFraw
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... € 37.930,-Keine Angabe False Audi: 06261-9282-555 VW+Skoda: 06261-9730-555 ... 2.653 km 12/2021 140 kW (190 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin - (l/100 km) - (g/km)
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... € 25.480,-Sehr guter Preis False Kundenkontaktcenter • DE-24944 Flensburg 8.400 km 08/2021 96 kW (131 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 5,1 l/100 km (komb.) 117 g/km (komb.)
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler € 24.489,-Fairer Preis False DE-39576 Stendal 19.700 km 05/2021 103 kW (140 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 146 g/km (komb.)
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... € 33.699,-Sehr guter Preis False - - • DE-46149 Oberhausen 12.103 km 01/2021 115 kW (156 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin 6,3 l/100 km 142 g/km (komb.)
12178 Dacia Spring Business NaN € 22.985,-Keine Angabe False Ihr Team der BOB Automobile • DE-45141 Essen 3.551 km 07/2021 33 kW (45 PS) Gebraucht 1 Fahrzeughalter Automatik Elektro - (l/100 km) 0 g/km (komb.)

12179 rows × 15 columns

Abschließend werden die gecrawlten Daten in einer postgreSQL Datenbank gesichert, sodass nicht bei jeder Programmausführung die Daten neu gecrawlt werden müssen.

Dafür muss zunächst eine Verbindung mit der Datenbank hergestellt werden. Das eingelesene JSON-File enthält die Datenbank-Parameter und muss von jedem Anwender mit seinen Zugangsdaten befüllt und im gleichen Dateipfad wie dieses Juypter-Notebook abgespeichert werden.

Das JSON-File benötigt folgende Informationen im gezeigten Format:
configLocalDS.json

#import json file for database connection parameters
with open('configLocalDS.json') as f:
    conf = json.load(f)

Anschließend wird unter Verwendung von sqlalchemy eine Verbindung zur Datenbank hergestellt.

conn_str ='postgresql://%s:%s@localhost:5432/%s'%(conf["user"], conf["passw"],conf["database"])
engine = create_engine(conn_str)

Das Dataframe wird nun in der Tabelle autoscout24cars in postgre SQL gespeichert, sofern die Tabelle noch nicht vorhanden ist.

if not engine.has_table("autoscout24cars"):
    AutoDFraw.to_sql(name='autoscout24cars',index=True, index_label='index',con=engine)
else:
    print("table already exists")
C:\Users\Admin\AppData\Local\Temp/ipykernel_6452/3504836538.py:1: SADeprecationWarning:

The Engine.has_table() method is deprecated and will be removed in a future release.  Please refer to Inspector.has_table(). (deprecated since: 1.4)
table already exists

Als Alternative zu SQL können die Daten in Excel gespeichert und wieder eingelesen werden um einen gleichbleibenden Datenstand zur Analyse zu gewährleisten:
Diese Codezeile ist hier auskommentiert, um das Backup nur bewusst überschreiben zu können.

#AutoDFraw.to_excel("AutoDF_raw.xlsx")

Die Fahrzeugdaten von Autoscout24 wurden erfolgreich abgezogen und in einem Dataframe gespeichert. Allerdings entsprechen viele Spalten noch nicht dem gewünschten Format, da beispielsweise Sonderzeichen enthalten sind oder numerische Werte nicht als solche erkannt werden.
Aus diesem Grund muss das Dataframe nun so bearbeitet werden, dass alle Spalten in einer für die Explorative Datenanalyse sinnvollen Struktur vorliegen.

Zunächst werden die in SQL gesicherten Daten in ein neues Dataframe AutoDF geladen.

AutoDF = pd.read_sql_query('SELECT * FROM autoscout24cars',engine, index_col="index")
AutoDF
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km
index
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... None € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
1 Renault Alpine A310 2.5 Turbo V6 None € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
2 Mercedes-Benz 190 190E 2.5-16 None € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP None € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde None € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... € 37.930,-Keine Angabe False Audi: 06261-9282-555 VW+Skoda: 06261-9730-555 ... 2.653 km 12/2021 140 kW (190 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin - (l/100 km) - (g/km)
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... € 25.480,-Sehr guter Preis False Kundenkontaktcenter • DE-24944 Flensburg 8.400 km 08/2021 96 kW (131 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 5,1 l/100 km (komb.) 117 g/km (komb.)
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler € 24.489,-Fairer Preis False DE-39576 Stendal 19.700 km 05/2021 103 kW (140 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin None 146 g/km (komb.)
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... € 33.699,-Sehr guter Preis False - - • DE-46149 Oberhausen 12.103 km 01/2021 115 kW (156 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin 6,3 l/100 km 142 g/km (komb.)
12178 Dacia Spring Business None € 22.985,-Keine Angabe False Ihr Team der BOB Automobile • DE-45141 Essen 3.551 km 07/2021 33 kW (45 PS) Gebraucht 1 Fahrzeughalter Automatik Elektro - (l/100 km) 0 g/km (komb.)

12179 rows × 15 columns

Alternativ können die Daten auch aus dem mitgelieferten Excel geladen werden. Um bei der Analyse die selben Ergebnisse zu gewährleisten, empfehlen wir diese Methode:

AutoDF=pd.read_excel("AutoDF_raw.xlsx",index_col=0)

Feature Engineering

Erzeugung zusätzlicher Variablen

Automarke

Eine für die Datenanalyse interessante Information ist die Automarke. Diese ist im Titel der Anzeige als erstes Wort enthalten. Daher wird zur Bestimmung der Automarke das erste Wort der Spalte Titel extrahiert und in einer neuen Spalte Marke gespeichert. Falls eine Automarke aus mehr als einem Wort besteht, wird lediglich das erste Wort übernommen.

# Erzeugen der Spalte "Marke" aus den Informationen der Spalte "Titel"
AutoDF['Marke'] = AutoDF['Titel'].str.split('\s+').str[0]

Ausstattung

In der Spalte Untertitel werden Ausstattungsmerkmale des Fahrzeugs aufgezählt. Einige ausgewählte Austattungsmerkmale werden als extra Spalten in das Dataframe aufgenommen. Dafür wird folgende Annahme getroffen: Ein Fahrzeug besitzt eine bestimmte Ausstattung, wenn diese in Spalte Untertitel erwähnt wird. Wird diese dort nicht erwähnt, besitzt ein Fahrzeug diese Ausstattung nicht. Dies wird mithilfe der Methode str.contains geprüft.

# Erzeugung zusätzlicher Variablen "Ausstattung" 
AutoDF['Alufelgen']= AutoDF['Untertitel'].str.contains("Alufelgen")
AutoDF['Sitzheizung']= AutoDF['Untertitel'].str.contains("Sitzheizung")
AutoDF['Klimaanlage']= (AutoDF['Untertitel'].str.contains("Klimaanlage")) | (AutoDF['Untertitel'].str.contains("Klimaautomatik"))
AutoDF['Einparkhilfe']= AutoDF['Untertitel'].str.contains("Einparkhilfe ")
AutoDF['Navigationssystem']= AutoDF['Untertitel'].str.contains("Navigationssystem")
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) Volkswagen NaN NaN False NaN NaN
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Renault NaN NaN False NaN NaN
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Mercedes-Benz NaN NaN False NaN NaN
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) BMW NaN NaN False NaN NaN
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Alfa NaN NaN False NaN NaN

Geodaten

Aus der Spalte Standort wird nun der Stadtname extrahiert, um eine spätere Kartendarstellung des Fahrzeugstandorts zu ermöglichen. Dies ist immer das letzt Wort der Spalte.

#Stadtname
AutoDF['Stadt'] = AutoDF['Standort'].str.split(' ').str[-1]
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) Volkswagen NaN NaN False NaN NaN Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Renault NaN NaN False NaN NaN LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Mercedes-Benz NaN NaN False NaN NaN MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) BMW NaN NaN False NaN NaN Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Alfa NaN NaN False NaN NaN VALENCIA

Entfernen unerwünschter Zeichen und Werte

Im nächsten Schritt wird das Dataframe um störende oder überflüssige Character bereinigt. Dazu gehören störende Satzzeichen, Währungen, Strings, etc.

Da in einzelnen Fällen zusätzlich optionale Leasingpreise noch hinter Kaufpreisen angezeigt werden (siehe Beispiel unten), müssen zuerst alle Zeichen hinter dem ersten Kaufpreis entfernt werden. Dann werden in der nächsten Codezeile alle weiteren nicht numerischen Zeichen entfernt.

AutoDF["Preis"].iloc[10730]
'€ 21.493,-Sehr guter Preisab €\xa0179,- mtl. / 60 Mon.'
# Entfernen aller Zeichen nach dem ersten ",-"
AutoDF['Preis'] = AutoDF['Preis'].replace('(,-).*', '',regex=True)
# Entfernen aller weiteren nicht numerischen Zeichen
AutoDF['Preis'] = AutoDF['Preis'].str.replace(r'[^0-9]+', '')

Da der Monat der Erstzulassung für die Analyse irrelevant ist, wird dieser sowie alle übrigbleibenden nicht numerischen Zeichen entfernt.

# Entfernen aller Zeichen vor "/" (Trennzeichen zwischen Monat und Jahr)
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace('.*/', '',regex=True)
# Entfernen aller weiteren nicht numerischen Zeichen
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace(r'[^0-9]+', '',regex=True)

Bei der PS-Angabe muss zuerst der Wert in kW entfernt werden, danach alle weiteren nicht numerischen Zeichen.

# Entfernen aller Zeichen vor "kW"
AutoDF['PS'] = AutoDF['PS'].replace(['.*kW'], '',regex=True)
# Entfernen aller weiteren nicht numerischen Zeichen
AutoDF['PS'] = AutoDF['PS'].replace(r'[^0-9]+', '',regex=True)

Die nachfolgenden Spalten enthalten im Datensatz noch Einheiten. Diese wurden bereits im Spaltentitel integriert (bspw. Emissionen_g_pro_km) und werden somit aus den Datensätzen entfernt, sodass nur noch numerische Werte verbleiben.

# Entfernen nicht numerischer Zeichen in "km"
AutoDF['km'] = AutoDF['km'].replace(r'[^0-9]+', '',regex=True)
# Entfernen nicht numerischer Zeichen in "Fahrzeughalter"
AutoDF['Fahrzeughalter'] = AutoDF['Fahrzeughalter'].replace(r'[^0-9]+', '',regex=True)
# Entfernen der Einheiten in "Verbrauch_l_pro_100km"
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(['\(l/100 km\)', 'l/100 km','\(komb.\)'], '',regex=True)
# Entfernen nicht numerischer Zeichen in "Emissionen_g_pro_km"
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].replace(r'[^0-9]+', '',regex=True)

Da Verbrauch und Emission bei Elektroautos in der Regel 0 ist, bei Brennstoff allerdings fehlerhafte Werte bedeuten würde, wird die Bereinigung wie folgt durchgeführt:

# Alle fehlenden Werte oder 0 Werte bei Verbrauch und Emissionen werden durch "NaN" ersetzt.
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(['-','','0'], np.NaN,regex=True)
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].replace(['-','','0'], np.NaN,regex=True)

# da keine Angabe oder 0 bei Verbrauch und Emissionen bei Elektroautos korrekt ist, wird der Wert für Elektroautos pauschal auf 0 gesetzt
AutoDF.loc[AutoDF.Kraftstoff == 'Elektro', 'Verbrauch_l_pro_100km'] = 0
AutoDF.loc[AutoDF.Kraftstoff == 'Elektro', 'Emissionen_g_pro_km'] = 0

Auch bei weiteren Spalten, bei denen die Angabe in der Autoscout Anzeige optional ist, müssen die fehlenden Werte durch “NaN” ersetzt werden.

# Weitere fehlende Werte werden durch "NaN" ersetzt
AutoDF['Fahrzeughalter'] = AutoDF['Fahrzeughalter'].replace(['-',''], np.NaN,regex=True)
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace(['-',''], np.NaN,regex=True)
AutoDF['km'] = AutoDF['km'].replace(['-',''], np.NaN,regex=True)
AutoDF['PS'] = AutoDF['PS'].replace(['-',''], np.NaN,regex=True)
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN 2950 False DE-92318 Neumarkt 139800 1990 90 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Volkswagen NaN NaN False NaN NaN Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN 23900 False Contáctanos en: • ES-27003 LUGO 73000 1990 200 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Renault NaN NaN False NaN NaN LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN 31500 False Contáctanos en: • ES-15800 MELIDE 90000 1990 194 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Mercedes-Benz NaN NaN False NaN NaN MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN 73900 False Ihr Verkaufsteam • DE-49076 Osnabrück 75156 1990 204 Gebraucht NaN Schaltgetriebe Benzin NaN NaN BMW NaN NaN False NaN NaN Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN 19900 False Contáctanos en: • ES-46006 VALENCIA 79000 1990 128 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Alfa NaN NaN False NaN NaN VALENCIA

In der Spalte Verbrauch_l_pro_100km wird das Komma zur Dezimaltrennung durch einen Punkt ersetzt, damit die Spalte später als float definiert werden kann.

AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(',', '.',regex=True)

Abgesehen von Klimaanlage entstehen “NaN” Values in den neu erzeugten Ausstattungsspalten wenn die Spalte Untertitel “NaN” ist, daher werden diese nun durch False ersetzt (Wir gehen davon aus, dass die Ausstattung nicht enthalten ist wenn sie nicht im Untertitel erwähnt ist).

AutoDF['Alufelgen'] = AutoDF['Alufelgen'].replace(np.NaN, False)
AutoDF['Sitzheizung'] = AutoDF['Sitzheizung'].replace(np.NaN, False)
AutoDF['Einparkhilfe'] = AutoDF['Einparkhilfe'].replace(np.NaN, False)
AutoDF['Navigationssystem'] = AutoDF['Navigationssystem'].replace(np.NaN, False)
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN 2950 False DE-92318 Neumarkt 139800 1990 90 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Volkswagen False False False False False Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN 23900 False Contáctanos en: • ES-27003 LUGO 73000 1990 200 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Renault False False False False False LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN 31500 False Contáctanos en: • ES-15800 MELIDE 90000 1990 194 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Mercedes-Benz False False False False False MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN 73900 False Ihr Verkaufsteam • DE-49076 Osnabrück 75156 1990 204 Gebraucht NaN Schaltgetriebe Benzin NaN NaN BMW False False False False False Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN 19900 False Contáctanos en: • ES-46006 VALENCIA 79000 1990 128 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Alfa False False False False False VALENCIA

In der Spalte Leasing werden die Werte noch mit 0.0 für False und 1.0 für True ausgegeben. Dies wird in Boolean Werte geändert.

AutoDF['Leasing'] = AutoDF['Leasing'].replace(0.0, False)
AutoDF['Leasing'] = AutoDF['Leasing'].replace(1.0, True)

Entfernen von fehlenden oder nicht benötigten Werten

Mit der .info() Methode werden nun alle Spalten des Dataframes sowie deren “Non-Null Count” angezeigt.

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 12179 entries, 0 to 12178
Data columns (total 22 columns):
 #   Column                 Non-Null Count  Dtype 
---  ------                 --------------  ----- 
 0   Titel                  12179 non-null  object
 1   Version                11900 non-null  object
 2   Untertitel             7238 non-null   object
 3   Preis                  12179 non-null  object
 4   Leasing                12179 non-null  bool  
 5   Standort               10300 non-null  object
 6   km                     12179 non-null  object
 7   Erstzulassung          12179 non-null  object
 8   PS                     12049 non-null  object
 9   Zustand                12179 non-null  object
 10  Fahrzeughalter         6218 non-null   object
 11  Getriebe               12179 non-null  object
 12  Kraftstoff             12179 non-null  object
 13  Verbrauch_l_pro_100km  8076 non-null   object
 14  Emissionen_g_pro_km    5059 non-null   object
 15  Marke                  12179 non-null  object
 16  Alufelgen              12179 non-null  bool  
 17  Sitzheizung            12179 non-null  bool  
 18  Klimaanlage            12179 non-null  bool  
 19  Einparkhilfe           12179 non-null  bool  
 20  Navigationssystem      12179 non-null  bool  
 21  Stadt                  10300 non-null  object
dtypes: bool(6), object(16)
memory usage: 1.6+ MB

Das Dataframe hat 21 Spalten. Davon haben die meisten den Datentyp object, obwohl es sich bei einigen davon um numerische Werte handelt. Dies muss noch geändert werden. Lediglich die Boolean Spalten wie beispielsweise Leasing wurden korrekt identifiziert.
Die meisten Spalten haben keine NULL Werte. Allerdings exisitieren auch Spalten, die sehr viele NULL-Werte aufweisen. Beispielsweise Emissionen_g_pro_km.
Nachfolgend werden die NULL-Werte in einer heatmap visuaisiert.

sns.set_theme(style="ticks", color_codes=True)

# Identifizieren der NULL Werte via Heatmap
sns.heatmap(AutoDF.isnull(), 
            yticklabels=False,
            cbar=False, 
            cmap='viridis');
_images/Projekt_64_0.png

In der Heatmap ist zu erkennen, dass sehr viele NULL-Werte in den Spalten Untertitel, Fahrzeughalter, Verbrauch_l_pro_100km und Emissionen_g_pro_km exisitieren.
Nachfolgend werden hierfür nochmal die exakten Mengen ausgegeben:

print(AutoDF.isnull().sum())
Titel                       0
Version                   279
Untertitel               4941
Preis                       0
Leasing                     0
Standort                 1879
km                          0
Erstzulassung               0
PS                        130
Zustand                     0
Fahrzeughalter           5961
Getriebe                    0
Kraftstoff                  0
Verbrauch_l_pro_100km    4103
Emissionen_g_pro_km      7120
Marke                       0
Alufelgen                   0
Sitzheizung                 0
Klimaanlage                 0
Einparkhilfe                0
Navigationssystem           0
Stadt                    1879
dtype: int64

Die Features Verbrauch_l_pro_100km, Emissionen_g_pro_km, km (Kilometerstand) und PS sollen in unserem Use Case genauer untersucht werden. Daher sollen im Folgenden alle Zeilen mit NULL Values entfernt werden.
Das Feature Fahrzeughalter soll später komplett entfernt werden, da dieses auch häufig nicht gepflegt wurde und auch nicht unbedingt aussagekräftig ist über den Zustand & Wert des Autos.

# Entfernen aller Spalten mit fehlenden Werten bei Verbrauch, Emissionen, km und PS
AutoDF = AutoDF[AutoDF['Verbrauch_l_pro_100km'].notna()]
AutoDF = AutoDF[AutoDF['Emissionen_g_pro_km'].notna()]
AutoDF = AutoDF[AutoDF['km'].notna()]
AutoDF = AutoDF[AutoDF['PS'].notna()]
print(AutoDF.isnull().sum())
Titel                       0
Version                    27
Untertitel                946
Preis                       0
Leasing                     0
Standort                  379
km                          0
Erstzulassung               0
PS                          0
Zustand                     0
Fahrzeughalter           1418
Getriebe                    0
Kraftstoff                  0
Verbrauch_l_pro_100km       0
Emissionen_g_pro_km         0
Marke                       0
Alufelgen                   0
Sitzheizung                 0
Klimaanlage                 0
Einparkhilfe                0
Navigationssystem           0
Stadt                     379
dtype: int64

Als nächstes sollen alle Leasing Fahrzeuge aus dem DF entfernt werden. Diese waren nur als Werbung zwischen den eigentlichen Gebrauchtwagen Angeboten enthalten und verfälschen mit bspw. Preis (pro Monat als Leasing) die Statistiken.

# Prüfung ob Leasing Fahrzeuge enthalten sind (Leasing == True)
AutoDF["Leasing"].unique()
array([False,  True])
# DF wird neu erstellt nur mit Datensätzen die Leasing == False sind (~AutoDF.Leasing)
AutoDF = AutoDF[~AutoDF.Leasing] 
# Prüfung ob alle Leasing Fahrzeuge entfernt sind
AutoDF["Leasing"].unique()
array([False])

Die Spalte Leasing wird nun nicht mehr benötigt und kann entfernt werden. Gleiches gilt für die Spalte Zustand, da alle Fahrzeuge gebraucht sind und die Spalte Standort, da die benötigte Information Stadt bereits daraus abgezogen wurde.
Wie oben erklärt, soll auch die Spalte Fahrzeughalter entfernt werden.

# Prüfung ob wirklich nur gebrauchte Fahrzeuge enthalten sind
AutoDF["Zustand"].unique()
array(['Gebraucht'], dtype=object)
# Entfernen der beschriebenen Spalten
AutoDF = AutoDF.drop(columns=['Zustand','Leasing','Fahrzeughalter','Standort'])

Anpassung der Datentypen

Zunächst wird geprüft, welche Datentypen aktuell vorliegen

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4570 entries, 7 to 12178
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype 
---  ------                 --------------  ----- 
 0   Titel                  4570 non-null   object
 1   Version                4543 non-null   object
 2   Untertitel             3627 non-null   object
 3   Preis                  4570 non-null   object
 4   km                     4570 non-null   object
 5   Erstzulassung          4570 non-null   object
 6   PS                     4570 non-null   object
 7   Getriebe               4570 non-null   object
 8   Kraftstoff             4570 non-null   object
 9   Verbrauch_l_pro_100km  4570 non-null   object
 10  Emissionen_g_pro_km    4570 non-null   object
 11  Marke                  4570 non-null   object
 12  Alufelgen              4570 non-null   bool  
 13  Sitzheizung            4570 non-null   bool  
 14  Klimaanlage            4570 non-null   bool  
 15  Einparkhilfe           4570 non-null   bool  
 16  Navigationssystem      4570 non-null   bool  
 17  Stadt                  4201 non-null   object
dtypes: bool(5), object(13)
memory usage: 522.2+ KB

Als nächstes werden die Datentypen angepasst, indem numerische Spalten einer Datentypkonvertierung unterzogen werden.

AutoDF['Preis'] = AutoDF['Preis'].astype('int')
AutoDF['km'] = AutoDF['km'].astype('int')
AutoDF['PS'] = AutoDF['PS'].astype('int')
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].astype('int')
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].astype('float')
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].astype('float')

Nun liegt das Dataframe in einer Form vor, in der die explorative Datenanalyse durchgeführt werden kann.

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4570 entries, 7 to 12178
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype  
---  ------                 --------------  -----  
 0   Titel                  4570 non-null   object 
 1   Version                4543 non-null   object 
 2   Untertitel             3627 non-null   object 
 3   Preis                  4570 non-null   int32  
 4   km                     4570 non-null   int32  
 5   Erstzulassung          4570 non-null   float64
 6   PS                     4570 non-null   int32  
 7   Getriebe               4570 non-null   object 
 8   Kraftstoff             4570 non-null   object 
 9   Verbrauch_l_pro_100km  4570 non-null   float64
 10  Emissionen_g_pro_km    4570 non-null   int32  
 11  Marke                  4570 non-null   object 
 12  Alufelgen              4570 non-null   bool   
 13  Sitzheizung            4570 non-null   bool   
 14  Klimaanlage            4570 non-null   bool   
 15  Einparkhilfe           4570 non-null   bool   
 16  Navigationssystem      4570 non-null   bool   
 17  Stadt                  4201 non-null   object 
dtypes: bool(5), float64(2), int32(4), object(7)
memory usage: 450.8+ KB

Die bereinigten Daten können an dieser Stelle optional noch einmal in einer neuen Tabelle in postgre SQL gesichert werden.

#if not engine.has_table("autoscout24cars-cleaned"):
#    AutoDF.to_sql(name='autoscout24cars-cleaned',index=True, index_label='index',con=engine)
#else:
#    print("table already exists")

Datenanalyse

Hinweis: Die folgende Analyse basiert auf den in der mitgelieferten Excel Liste gespeicherten Daten. Diese wurden am 12.09.2022 mit der oben aufgeführten Methode gecrawlt.
Wenn das komplette Notebook mit Crawling neu ausgeführt wird, können daher unterschiedliche Ergebnisse entstehen (bspw. Anteil Automarken).

Wir empfehlen daher, mit der mitgelieferten Excel Liste zu arbeiten (Methode zum einlesen siehe oben).

Vorbereitung & allgemeine Untersuchung des DF

Nachfolgend werden alle numerischen Features in einer Liste gespeichert.

num_features=AutoDF.select_dtypes(include=np.number).columns.to_list()
num_features
['Preis',
 'km',
 'Erstzulassung',
 'PS',
 'Verbrauch_l_pro_100km',
 'Emissionen_g_pro_km']

Gleiches wird für alle nicht numerischen Features durchgeführt.

cat_features=AutoDF.select_dtypes(exclude=np.number).columns.to_list()
cat_features
['Titel',
 'Version',
 'Untertitel',
 'Getriebe',
 'Kraftstoff',
 'Marke',
 'Alufelgen',
 'Sitzheizung',
 'Klimaanlage',
 'Einparkhilfe',
 'Navigationssystem',
 'Stadt']

Für einen ersten Überblick über die Datenverteilung numerischer Features bietet sich die describe() Methode an. Diese gibt für jede Spalte die Anzahl, Durchschnitt, Standardabweichung, Minimum, Maximum sowie die Quartile an.

AutoDF.describe().transpose()
count mean std min 25% 50% 75% max
Preis 4570.0 22212.728228 24413.752948 1.0 7450.00 15999.0 27990.0 349900.0
km 4570.0 112090.814004 78410.721967 7.0 52954.25 98500.0 157000.0 729439.0
Erstzulassung 4570.0 2010.482932 7.251224 1990.0 2005.00 2011.0 2017.0 2021.0
PS 4570.0 196.174398 106.986416 5.0 125.00 165.0 239.0 772.0
Verbrauch_l_pro_100km 4570.0 7.351116 2.919708 0.0 5.50 6.7 8.8 61.0
Emissionen_g_pro_km 4570.0 177.867396 65.947109 0.0 135.00 163.0 213.0 595.0

Hier fällt bereits auf, dass teils extreme Werte vorliegen, wie bspw. max Werte beim Kilometerstand von 729.439km oder Fahrzeuge mit einem Verbrauch von 61 Liter auf 100km. Diese Werte fallen als extrem auf, da sie sehr stark vom vierten Quartil (75%) abweichen.
Der Durchschnittspreis der von uns Untersuchten Fahrzeuge liegt bei ca. 22.000€ und der durchschittliche Kilometerstand bei ca 112.000km. Die extremen Fälle lohnt es sich ggfs. genauer anzuschauen. Bei dem Beispiel mit dem besonders hohen Verbrauch fällt direkt auf, dass es sich um einen Tippfehler handeln muss.

AutoDF.loc[AutoDF['Verbrauch_l_pro_100km']>30]
Titel Version Untertitel Preis km Erstzulassung PS Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
9364 Mercedes-Benz E 200 T AVANTGARDE STANDHZ,SCHECKHEFT,PARKASSIST LED-Scheinwerfer, Einparkhilfe Sensoren hinten... 22680 87036 2014.0 184 Automatik Benzin 61.0 142 Mercedes-Benz False True False True True Schönefeld

Der Tippfehler kann manuell korrigiert werden:

# Wert wird manuell angepasst
AutoDF.at[9364, 'Verbrauch_l_pro_100km'] = 6.1
# Ausgabe des Datensatzes zur Überprüfung
print(AutoDF.loc[9364])
Titel                                                 Mercedes-Benz E 200 
Version                         T AVANTGARDE STANDHZ,SCHECKHEFT,PARKASSIST
Untertitel               LED-Scheinwerfer, Einparkhilfe Sensoren hinten...
Preis                                                                22680
km                                                                   87036
Erstzulassung                                                       2014.0
PS                                                                     184
Getriebe                                                         Automatik
Kraftstoff                                                          Benzin
Verbrauch_l_pro_100km                                                  6.1
Emissionen_g_pro_km                                                    142
Marke                                                        Mercedes-Benz
Alufelgen                                                            False
Sitzheizung                                                           True
Klimaanlage                                                          False
Einparkhilfe                                                          True
Navigationssystem                                                     True
Stadt                                                           Schönefeld
Name: 9364, dtype: object
AutoDF.loc[AutoDF['km']>600000]
Titel Version Untertitel Preis km Erstzulassung PS Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
7489 Mercedes-Benz E 220 Combi CDI Avantgarde 2009 Armlehne, Radio, Servolenkung, Dachreling, Lor... 3750 729439 2009.0 170 Automatik Diesel 7.3 194 Mercedes-Benz False False True False False RUINERWOLD

Nachfolgend wird die Anzahl der unique values je Feature ausgegeben.

for col in AutoDF.columns:
    values = AutoDF[col].unique()
    print(col, "has", len(AutoDF[col].unique()), "unique values")
Titel has 785 unique values
Version has 4311 unique values
Untertitel has 3372 unique values
Preis has 1817 unique values
km has 3334 unique values
Erstzulassung has 32 unique values
PS has 277 unique values
Getriebe has 4 unique values
Kraftstoff has 7 unique values
Verbrauch_l_pro_100km has 157 unique values
Emissionen_g_pro_km has 276 unique values
Marke has 63 unique values
Alufelgen has 2 unique values
Sitzheizung has 2 unique values
Klimaanlage has 2 unique values
Einparkhilfe has 2 unique values
Navigationssystem has 2 unique values
Stadt has 1067 unique values

Alle Features haben mindestens zwei verschiedene Ausprägungen. Variablen mit nur einem “unique value” würden keinen Mehrwert liefern für unsere Untersuchung.
Außerdem können wir bspw. erkennen, dass 63 verschiedene Automarken vertreten sind und die Fahrzeuge aus 1067 verschiedenen Städten angeboten werden und aus 32 unterschiedlichen Jahren stammen.

Als nächstes wird die Anzahl der Fahrzeuge pro Kraftstoffart ausgegeben.

print(AutoDF['Kraftstoff'].value_counts())
Benzin            3141
Diesel            1267
Elektro/Benzin      81
Elektro             38
Autogas (LPG)       21
Elektro/Diesel      21
Ethanol              1
Name: Kraftstoff, dtype: int64

Die häufigste Kraftstoffart ist Benzin, gefolgt von Diesel.
Alle anderen Kraftstoffarten kommen in Relation zur Gesamtmenge an Fahrzeugen eher selten vor.

Ebenso interessant ist die Anzahl der Fahrzeuge pro Getriebeart.

print(AutoDF['Getriebe'].value_counts())
Schaltgetriebe    2346
Automatik         2200
Halbautomatik       17
- (Getriebe)         7
Name: Getriebe, dtype: int64

Automatik und Schaltgetriebe kommen ungefähr gleich oft vor. Halbautomatikfahrzeuge kommen dagegen eher selten vor.
Außerdem fällt hier auf, dass 7 Datensätze keinen Wert haben für Getriebe - diese Datensätze werden als nächstes entfernt.

# Dataframe wird erstellt nur mit vorhandenen Werten bei "Getriebe"
AutoDF=AutoDF[AutoDF['Getriebe'].str.contains('- \(Getriebe\)')==False]
print(AutoDF['Getriebe'].value_counts())
Schaltgetriebe    2346
Automatik         2200
Halbautomatik       17
Name: Getriebe, dtype: int64

Nachfolgend wird der prozentuale Anteil jeder Automarke in Bezug auf die Gesamtmasse aller Fahrzeuge ausgegeben.

print(AutoDF['Marke'].value_counts(normalize=True))
Audi             0.146833
BMW              0.135437
Mercedes-Benz    0.133684
Volkswagen       0.129520
Opel             0.050405
Ford             0.047556
Porsche          0.032435
Volvo            0.028490
MINI             0.021696
Skoda            0.020820
Peugeot          0.018190
Toyota           0.017532
SEAT             0.015779
Mazda            0.015560
Renault          0.015560
Jaguar           0.013807
Hyundai          0.012053
Alfa             0.011834
Kia              0.011615
Nissan           0.010958
Fiat             0.010739
Citroen          0.008766
Land             0.007890
Honda            0.007890
Cupra            0.007670
smart            0.005260
Mitsubishi       0.005041
Dacia            0.004821
Aston            0.004602
Saab             0.004602
Suzuki           0.004383
Jeep             0.003945
Maserati         0.003726
Chevrolet        0.003287
Lexus            0.003287
Ferrari          0.002192
Dodge            0.002192
Subaru           0.001972
Bentley          0.001972
Abarth           0.001534
SsangYong        0.001534
Alpina           0.001315
Daihatsu         0.001315
HUMMER           0.001096
Chrysler         0.001096
Lancia           0.000877
MG               0.000877
Sonstige         0.000877
Cadillac         0.000657
Daewoo           0.000657
DS               0.000438
Maybach          0.000438
Rover            0.000438
Lamborghini      0.000438
Tesla            0.000438
Infiniti         0.000438
Lada             0.000219
Corvette         0.000219
Lincoln          0.000219
Aixam            0.000219
Rolls-Royce      0.000219
Caterham         0.000219
Polestar         0.000219
Name: Marke, dtype: float64

Die häufigste in unserem Abzug vorkommende Automarke ist Audi, dicht gefolgt von von BMW und Mercedes-Benz.
Sehr selten vorkommende Automarken sind bspw. Lincoln, Aixam und Caterham.

Um allgemein noch einen guten Überblick über die Verteilung der numerischen Variablen zu bekommen, werden Histogramme erzeugt.

# Erstellen von Histogrammen der numerischen Variablen
AutoDF.hist(bins=20, figsize=(20,15))
plt.show("notebook")
_images/Projekt_114_0.png

Für einzelne Variablen wie bspw. km oder PS lassen sich rechtsschiefe Verteilungen erkennen. Vor allem bei Erstzulassung lässt sich aber kein Schwerpunkt in der Verteilung erkennen, lediglich ein leichter Trend zu weniger alten Fahrzeugen.
Da vor allem der Preis für diese Untersuchung interessant ist, wird dieses Histogramm noch einmal detaillierter dargestellt.

#Erstellung eines detaillierten Histogramm zur Variable Preis
fig = px.histogram(AutoDF, x="Preis",title="Distribution over price (Euro)")
fig.show()

Korrelationsanalyse

Um eine erste Übersicht über mögliche Zusammenhänge zwischen den verschiedenen Variablen zu erhalten, wird zunächst ein Pairplot erstellt. Der Plot eignet sich besonders für numerische Variablen, durch farbliche Markierung kann allerdings auch eine kategoriale Variable dargestellt werden.

sns.pairplot(data=AutoDF, vars=["Preis","PS","km","Erstzulassung","Verbrauch_l_pro_100km","Emissionen_g_pro_km"],
             hue="Kraftstoff",)
<seaborn.axisgrid.PairGrid at 0x1bc49f5ecd0>
_images/Projekt_119_1.png

Tatsächlich können im Pairplot Zusammenhänge zwischen einzelnen Variablen erkannt werden. Vor allem die starke Korrelation zwischen Verbrauch und Emissionen fällt im Plot auf, ist allerdings selbstverständlich da mit höherem Verbrauch in der Regel auch mehr Emissionen erzeugt werden. Doch auch weniger starke Abhängigkeiten können erkannt werden wie bspw. zwischen PS und Preis oder zwischen PS und Verbrauch.

Durch die farbliche Markierung der Kraftstoffart lässt sich hier auch schon gut erkennen, dass Benzin Fahrzeuge eher einen höheren Verbrauch und höhere Emissionen erzeugen als Diesel Fahrzeuge. Auch Autogas scheint tendentiell mehr Emissionen zu erzeugen als Diesel Fahrzeuge.

Nach der optischen Darstellung sollen nun im nächsten Schritt die Abhängigkeiten noch einmal in Zahlen dargestellt werden.

# Erstellen einer Korrelationsmatrix
corr_matrix = AutoDF.corr()
corr_matrix
Preis km Erstzulassung PS Verbrauch_l_pro_100km Emissionen_g_pro_km Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem
Preis 1.000000 -0.421786 0.375126 0.701054 0.243266 0.232313 -0.147829 0.002652 -0.089297 0.037044 0.041534
km -0.421786 1.000000 -0.568111 -0.127463 0.220413 0.255970 0.108523 -0.119208 -0.031420 -0.153058 -0.142417
Erstzulassung 0.375126 -0.568111 1.000000 0.117229 -0.517793 -0.506678 -0.055640 0.201850 0.068506 0.311526 0.315454
PS 0.701054 -0.127463 0.117229 1.000000 0.586000 0.579788 -0.141854 0.011839 -0.142537 0.015200 0.062163
Verbrauch_l_pro_100km 0.243266 0.220413 -0.517793 0.586000 1.000000 0.971967 -0.046500 -0.084380 -0.081249 -0.160558 -0.129709
Emissionen_g_pro_km 0.232313 0.255970 -0.506678 0.579788 0.971967 1.000000 -0.043258 -0.079571 -0.082951 -0.153974 -0.123025
Alufelgen -0.147829 0.108523 -0.055640 -0.141854 -0.046500 -0.043258 1.000000 0.055907 0.179344 0.042522 0.047007
Sitzheizung 0.002652 -0.119208 0.201850 0.011839 -0.084380 -0.079571 0.055907 1.000000 0.125800 0.203946 0.244613
Klimaanlage -0.089297 -0.031420 0.068506 -0.142537 -0.081249 -0.082951 0.179344 0.125800 1.000000 0.065015 0.099932
Einparkhilfe 0.037044 -0.153058 0.311526 0.015200 -0.160558 -0.153974 0.042522 0.203946 0.065015 1.000000 0.320108
Navigationssystem 0.041534 -0.142417 0.315454 0.062163 -0.129709 -0.123025 0.047007 0.244613 0.099932 0.320108 1.000000
# Erstellen einer Heatmap um Abhängigkeiten zwischen den verschiedenen Variablen zu visualisieren

# Einstellung um nur den relevanten Teil der Matrix zu plotten
mask = np.zeros_like(corr_matrix)
mask[np.triu_indices_from(mask)]= True

# Erstellen der Heatmap
plt.subplots(figsize=(11, 15))
heatmap = sns.heatmap(corr_matrix, 
                      mask = mask, 
                      square = True, 
                      linewidths = .5,
                      cmap = 'coolwarm',
                      cbar_kws = {'shrink': .6,
                                'ticks' : [-1, -.5, 0, 0.5, 1]},
                      vmin = -1,
                      vmax = 1,
                      annot = True,
                      annot_kws = {"size": 10})
_images/Projekt_122_0.png

Durch die Korrelationsmatrix sowie deren Visualisierung mit einer Heatmap können Korrelationen zwischen den verschiedenen Variablen auf einen Blick erkannt werden. Die farblich besonders saturierten Felder (dunkelblau und dunkelrot) weisen auf besonders starke Abhängigkeit hin.

Da die offensichtlichsten Korrelationen (bspw. zwischen PS, Verbrauch und Emissionen) naheliegend und daher nicht besonders interessant sind, soll im Folgenden näher untersucht werden, welche Faktoren einen besonderen Einfluss auf den Preis haben.

# Berechnung der Korrelationen der einzelnen Variablen zur Variable "Preis"
corr = AutoDF.corr()
corr['Preis'].sort_values(ascending=False)
Preis                    1.000000
PS                       0.701054
Erstzulassung            0.375126
Verbrauch_l_pro_100km    0.243266
Emissionen_g_pro_km      0.232313
Navigationssystem        0.041534
Einparkhilfe             0.037044
Sitzheizung              0.002652
Klimaanlage             -0.089297
Alufelgen               -0.147829
km                      -0.421786
Name: Preis, dtype: float64

Von den numerischen Variablen haben PS, km und Erstzulassung den höchsten Einfluss auf den Preis.
Diese Variablen sollen daher näher untersucht werden:

# Erstellung Scatterplot zu PS und Preis mit Trendlinie, hover data und zusätzlicher Visualisierung von Verbrauch und Emissionen
fig=px.scatter(AutoDF,x="PS",y="Preis",color="Emissionen_g_pro_km",size="Verbrauch_l_pro_100km",
              hover_data=["Marke","Titel","Kraftstoff"],title="Price over PS",
              trendline="ols")
fig.show()
# Plot mit Trendlinie für km & Preis
sns.lmplot(x='km', y='Preis', data=AutoDF, 
line_kws={'color': 'darkred'}, ci=False);
_images/Projekt_127_0.png
# Plot mit Trendlinie für Erstzulassung & Preis
sns.lmplot(x='Erstzulassung', y='Preis', data=AutoDF, 
line_kws={'color': 'darkred'}, ci=False)
<seaborn.axisgrid.FacetGrid at 0x1bc4edfd4f0>
_images/Projekt_128_1.png

Zusätzlich wird hier noch ein Boxplot erstellt, an welchem die Verteilung sowie Ausreißer besser erkannt werden können. Hier fällt auf, dass Ausreißer vor allem Luxusmarken wie Lamborghini, Bentley oder Porsche sind oder sehr teuere Modelle von bspw. BMW (BMW Z8).

# Erstellung eines Boxplots zur Preis & Erstzulassung
fig = px.box(data_frame=AutoDF,x="Erstzulassung", y="Preis",
                 hover_name="Titel")
fig.show()

Wie auch zuvor schon vermutet lässt sich hier nochmal klar bestätigen (durchschnittlich):
1. Mit steigenden PS steigt auch der Preis (positive Korrelation)
2. Mit steigendem Kilometerstand sinkt der Preis (negative Korrelation)
3. Mit steigendem Jahr der Erstzulassung steigt auch der Preis (positive Korrelation)

Marke & Preis

Mit Hilfe eines Boxplots wollen wir außerdem untersuchen, wie sich die Preisverteilung bei den unterschiedlichen Automarken verhält. Um das besonders übersichtlich zu gestalten, wird die Anzeige aufsteigend nach dem Durchschnittspreis je Marke sortiert:

#sortieren der Marken nach Durchschnittspreis
sorted_nb = AutoDF.groupby(['Marke'])['Preis'].median().sort_values()
#sorted_nb
#Anpassen der seaborn Plotgröße um den Plot übersichtlich darzustellen
sns.set(rc={'figure.figsize':(15,15)})
#Erzeugen des Plots
sns.boxplot(x=AutoDF['Preis'], y=AutoDF['Marke'], order=list(sorted_nb.index),orient="h")
<AxesSubplot:xlabel='Preis', ylabel='Marke'>
_images/Projekt_135_1.png

Am Plot mit Sortierung lässt sich gut erkennen, dass Luxus-Automarken wie Lamborghini, Aston Martin und Rolls-Royce auch bei gebrauchten Fahrzeugen im Schnitt die teuersten Angebote darstellen. Vor allem bei Lamborghini und Aston Martin fällt auf, dass die Preisspanne innerhalb der vier Quartile des Boxplots sehr groß ist.
Günstige Fahrzeuge werden von den Marken Daewoo, Daihatsu und Rover angeboten. Am Beispiel Rover können wir erkennen, dass auf Grund einer geringen Auswahl an Fahrzeugangeboten mit einem sehr hohen Kilometerstand schnell ein sehr geringer durschnittlicher Preis entstehen kann:

AutoDF.loc[AutoDF['Marke']=='Rover']
Titel Version Untertitel Preis km Erstzulassung PS Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
3558 Rover 75 2.0 V6 Club Leer Memory Clima . NaN 1650 303358 1999.0 150 Schaltgetriebe Benzin 9.4 232 Rover False False False False False APELDOORN
3903 Rover 75 2.5 V6 Sterling 177Pk Automaat (6 CILINDER, CL... NaN 4945 157651 2000.0 177 Automatik Benzin 11.6 286 Rover False False False False False IJssel

Ausstattung & Preis

Als nächstes soll untersucht werden, inwiefern die Ausstattungsmerkmale einen eindeutigen Einfluss auf den Preis haben.

Austtattung=['Klimaanlage','Alufelgen','Sitzheizung','Einparkhilfe','Navigationssystem']
Austtattung
['Klimaanlage',
 'Alufelgen',
 'Sitzheizung',
 'Einparkhilfe',
 'Navigationssystem']
fig, ax = plt.subplots(2, 3, figsize=(15, 10))
for var, subplot in zip(Austtattung, ax.flatten()):
    sns.boxplot(x=var, y='Preis', data=AutoDF, ax=subplot)
_images/Projekt_141_0.png

Durch die Boxplots lässt sich leider kaum eine Auswirkung der Ausstattungsmerkmale auf den Preis erkennen. Vermutlich ist die Beschreibung der Ausstattung oft nicht detailliert gepflegt. Man kann vermuten, dass vor allem bei modernen und teuren Autos eine Klimaanlage bspw. selbstverständlich ist und daher nicht extra im Untertitel der Anzeige erwähnt wird.

Kraftstoff & Preis

# Gruppieren des DF nach Kraftstoff mit durschnittlichem Preis
PriceAveragePerKraftstoff=AutoDF.groupby(by="Kraftstoff")["Preis"].mean()
# Erzeugen des Bar Plots mit den zuvor erzeugten Gruppierungen
PriceAveragePerKraftstoff.plot(kind="bar",figsize=(12,6),color="m",title="Durschnittlicher Preis nach Kraftstoff")
<AxesSubplot:title={'center':'Durschnittlicher Preis nach Kraftstoff'}, xlabel='Kraftstoff'>
_images/Projekt_144_1.png

Am Balkendiagramm können wir erkennen, dass Elektrofahrzeuge und Hybride im Durschnitt am teuersten gehandelt werden, Fahrzeuge mit Ethanol oder Autogas als Kraftstoff am günstigsten.
Da bei diesen Untersuchungen allerdings auch die Anzahl an vorliegenden Datensätzen eine Rolle spielt, wird diese noch einmal zusätzlich in einem weiteren Plot visualisiert:

#Anpassen der seaborn Plotgröße um den Plot übersichtlich darzustellen
sns.set(rc={'figure.figsize':(12,8)})
sns.stripplot(data=AutoDF, x="Kraftstoff", y="Preis" , size=3 )
<AxesSubplot:xlabel='Kraftstoff', ylabel='Preis'>
_images/Projekt_146_1.png

Hier können wir erkennen, dass nur für Benzin und Diesel sehr viele Datensätze vorliegen - vor allem Ethanol scheint nur durch einen Datensatz vertreten zu sein und sollte daher mit Vorsicht interpretiert werden.

Getriebe & Preis

Als nächstes wird der Einfluss der Art des Getriebes auf den Preis untersucht:

# Erstellen eines erweiterten Boxplots zu Getriebe & Preis
sns.boxenplot(data=AutoDF, x="Getriebe", y="Preis")
<AxesSubplot:xlabel='Getriebe', ylabel='Preis'>
_images/Projekt_150_1.png

Am Boxplot können wir erkennen, das Fahrzeuge mit Schaltgetriebe im Durchschnitt am günstigsten verkauft werden, Fahrzeuge mit Automatik im Durchschnitt am teuersten.
Das kann noch etwas detaillierter dargstellt werden:

#Erstellen einzelner DF nach Getriebe für folgende Visualisierung
Automatik=AutoDF[AutoDF["Getriebe"]=="Automatik"]
Schaltgetriebe=AutoDF[AutoDF["Getriebe"]=="Schaltgetriebe"]
Halbautomatik=AutoDF[AutoDF["Getriebe"]=="Halbautomatik"]
#Erstellen von Series Objekten mit den entsprechenden Preisen
npAutomatik = Automatik["Preis"]
npSchaltgetriebe = Schaltgetriebe["Preis"]
npHalbautomatik = Halbautomatik["Preis"]
# Zusammenfassen der Series Objekte zu einer Liste
data = [npAutomatik.values, npSchaltgetriebe.values, npHalbautomatik.values]

# Parameter für Plot
group_labels = ['Automatik', 'Schaltgetriebe', 'Halbautomatik']
colors = ['#462EDE', '#DE2EBE', '#FF8033']

# Erstellen des Plots mit zuvor erzeugter Liste und Parametern
fig = ff.create_distplot(data, group_labels, 
                         bin_size=3000, show_rug=False)

# Anpassung Titetl
fig.update_layout(title_text='Preisverteilung nach Getriebe')
fig.show()

Hier fällt auf, dass bei Halbautomatik sich die Angebote nicht in einem Bereich verteilen, sondern sich bei bestimmten Preisen gruppieren. Bei den anderen Getriebearten liegt dagegen ein rechtsschiefe Normalverteilung vor.

Untersuchung von Unterschieden zwischen Automarken

Neben den zuvor schon festgestellten Unterschieden im Preis sollen nun auch noch weitere Unterschiede zwischen den Automarken untersucht werden. Dazu werden die numerischen Variablen der Marken zunächst nach dem Durchschnitt gruppiert.

MarkenGruppiert=AutoDF.groupby(by="Marke").mean()
#MarkenGruppiert
# Erstellen eines Barcharts zum durchschnittlichen Verbrauch pro Marke
fig=px.bar(x=MarkenGruppiert.index,y=MarkenGruppiert["Verbrauch_l_pro_100km"],title="Durchschnittlicher Verbrauch pro Automarke (Liter/100km)")
fig.show()

Hier fällt auf, dass Luxus- & Sportmarken wie bspw. Ferrari, Lamborghini und Bentley im Schnitt den höchsten Verbrauch haben. Reine Elektromarken wie Polestar oder Tesla haben logischerweise einen durchschnittlichen Verbrauch von 0.

Als nächstes soll die Eigenschaft PS untersucht werden auf Unterschiede zwischen den Automarken.

#sortieren der Marken nach Durchschnitts PS
sorted_ps = AutoDF.groupby(['Marke'])['PS'].median().sort_values()
#Anpassen der Größe des Plots
sns.set(rc={'figure.figsize':(15,15)})
#Erzeugen des Plots
sns.boxplot(x=AutoDF['PS'], y=AutoDF['Marke'], order=list(sorted_ps.index),orient="h")
<AxesSubplot:xlabel='PS', ylabel='Marke'>
_images/Projekt_161_1.png

Ähnlich wie beim Verbrauch fallen auch hier bei den PS Luxusmarken wie Lamborghini & Bentley auf. Interessant ist hier jedoch, das auch Maybach sehr weit vorne dabei ist, zuvor jedoch nicht beim Verbrauch in den Top 3 gelandet ist (lediglich Platz 9 wie in Liste unten zu sehen ist). Maybach scheint daher eher effiziente Motoren zu verwenden.

sorted_verbrauch = AutoDF.groupby(['Marke'])['Verbrauch_l_pro_100km'].median().sort_values()
sorted_verbrauch
Marke
Aixam             0.00
Dacia             0.00
Tesla             0.00
Polestar          0.00
Sonstige          2.40
smart             4.90
Daihatsu          4.90
SEAT              5.40
Skoda             5.40
Cupra             5.50
DS                5.70
Ford              6.00
Kia               6.10
MINI              6.10
Volkswagen        6.10
SsangYong         6.20
Opel              6.20
Lada              6.20
Citroen           6.20
Peugeot           6.40
Audi              6.40
Nissan            6.40
Fiat              6.40
Suzuki            6.40
Mitsubishi        6.60
Chevrolet         6.60
Toyota            6.65
Mazda             6.70
Volvo             6.75
Abarth            6.80
Renault           7.00
Daewoo            7.00
Hyundai           7.20
Honda             7.20
Mercedes-Benz     7.45
MG                7.50
BMW               7.55
Chrysler          7.80
Caterham          8.30
Subaru            8.60
Alpina            8.70
Alfa              8.75
Lancia            8.80
Infiniti          9.05
Jeep              9.10
Saab              9.40
Land              9.50
Lexus             9.80
Rover            10.50
Porsche          11.30
Dodge            11.60
Lincoln          11.90
Jaguar           11.90
Corvette         12.30
Maybach          13.80
Cadillac         14.70
Maserati         14.70
Rolls-Royce      15.90
Aston            16.40
Bentley          17.10
HUMMER           18.10
Ferrari          18.60
Lamborghini      20.45
Name: Verbrauch_l_pro_100km, dtype: float64

Identifikation von guten Angeboten

Eine weitere nützliche Analyse könnte die Identifikation von attraktiven Angeboten auf Basis ausgewählter Attribute sein.

Da in der EDA festgestellt wurde, dass PS und km den größten Einfluss auf den Preis haben, sollen diese drei Attribute visualisiert werden. Zusätzlich wird der Kraftstoff farblich hervorgehoben, da dieser bei einer Kaufentscheidung auch eine Rolle spielt.

Ein gutes Angebot findet sich demnach mit niedrigem Preis und Kilometerstand und viel PS in der entsprechenden Ecke des unten dargestellten Plots. Weitere relevante Details werden via “hover_data” angezeigt.

fig = px.scatter_3d(AutoDF, x='Preis', y='km', z='PS',
              color='Kraftstoff',hover_data=["Marke","Titel","Erstzulassung"],width=900,height=800)

fig.update_traces(marker=dict(size=4,
                              line=dict(width=1,
                              color='DarkSlateGrey')),
                  )
fig.show("notebook")

Auffällig ist vor allem ein Fahrzeug mit besonders wenig km, PS und Preis. Hierbei handelt es sich wohl nicht um ein Auto :)

AutoDF.loc[AutoDF['PS']==5]
Titel Version Untertitel Preis km Erstzulassung PS Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
3236 Sonstige Marken City-EL ohne Batterie! MOPED viele TEILE NaN 2290 8700 1998.0 5 Automatik Elektro 0.0 0 Sonstige False False False False False Kamenz

Kartenvisualisierung

Als nächstes wird der Standort der zum Verkauf angebotenen Fahrzeuge in einer Landkarte visualisiert.
Für diese Zwecke wurde beim Webcrawling das Attribut Standort von Autoscout24 abgezogen und anschließend in der Datenaufbereitung der Stadtname als extra Spalte angelegt.
Mithilfe des Geolocators werden jeder Stadt Longitude und Latitude zugeordnet und im Dataframe geoDF gespeichert. Dieses Dataframe wird anschließend über den Index mit dem AutoDF gejoined.

Aufgrund der Datenmenge wird für die Kartenvisualisierung nur ein Ausschnitt von 100 Datenpunkte verwendet. Diese werden in AutoDFsmall gespeichert.

AutoDFsmall = AutoDF[0:100]
# Schleife um Geodaten zu Städtenamen zu erhalten und diese im GeoDF zu speichern
geolocator = Nominatim(user_agent="my_app")
geoDF = pd.DataFrame()
for city in AutoDFsmall.index:
    try:
        location = geolocator.geocode(AutoDFsmall['Stadt'][city])
        geoDF = geoDF.append({"longitude": location.longitude, "latitude": location.latitude}, ignore_index=True)  
    except:
        geoDF = geoDF.append({"longitude": None, "latitude": None}, ignore_index=True)
geoDF.head()
longitude latitude
0 13.322287 52.457257
1 9.278869 45.639544
2 11.048029 46.314475
3 8.816553 50.520168
4 8.764870 51.718960
#Index von AutoDF zurücksetzen, da aufgrund der Entfernung der Null-Values bei Verbrauch und Emissionen viele Zeilen weggefallen sind
#sonst kann nicht mit GeoDF gejoined werden
AutoDFsmall = AutoDFsmall.reset_index(drop=True)

#Join von GeoDF und AutoDF über Index
AutoDFsmall = pd.merge(AutoDFsmall, geoDF, left_index=True, right_index=True)

Unter Verwendung von Longitude und Latitude werden die Fahrzeugstandorte auf einer Folium Map visualisiert.
Zusätzlich wird jedem Datenpunkt eine Pop-Up Beschreibung hinzugefügt, welche Stadtname, Autobeschreibung und Preis beinhaltet.

# Erstellen der Map mit Datenpunkten
m = folium.Map([50.0 , 10.0],zoom_start=4)
for i in AutoDFsmall.index:
    try:
        folium.Marker( location=[ AutoDFsmall['latitude'][i], AutoDFsmall['longitude'][i] ], popup = [AutoDFsmall['Stadt'][i], AutoDFsmall['Titel'][i], AutoDFsmall['Preis'][i]]).add_to(m)
    except:
        continue
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Regression

In diesem Kapitel wird ein einfaches Regressionsmodell zur Bestimmmung des Fahrzeugpreises auf Basis ausgewählter Features berechnet.
Hierfür wird die lineare Regression gewählt. Da die Regression nicht Fokus des Projekts ist, wurde auf einen Split der Daten in Trainings- und Testdaten verzichtet.
Zudem wird lediglich ein Modell berechnet und somit auf den Vergleich verschiedener Modell mit anschließender Auswahl des besten Modells verzichtet.
Für das Modell wurden jene Features ausgewählt, die in der vorherigen explorativen Datenanalyse die größte Korrelation zu Preis aufweisen.
Dazu gehören folgende Features:

  • PS

  • km

  • Erstzulassung

  • Kraftstoff

  • Verbrauch_l_pro_100km

  • Emissionen_g_pro_km

Da die Features Verbrauch und Emissionen eine sehr hohe Korrelation untereinander aufweisen, soll nur eines der beiden Features verwendet werden. Da die Abhängigkeit von Preis zu Verbrauch minimal höher ist als zu Emissionen, wird der Verbrauch gewählt.

# Fit Model
lm = smf.ols(formula='Preis ~ PS + km + Kraftstoff + Erstzulassung + Verbrauch_l_pro_100km', data=AutoDF).fit()
# Full summary
lm.summary()
OLS Regression Results
Dep. Variable: Preis R-squared: 0.634
Model: OLS Adj. R-squared: 0.633
Method: Least Squares F-statistic: 789.3
Date: Wed, 14 Sep 2022 Prob (F-statistic): 0.00
Time: 18:45:18 Log-Likelihood: -50279.
No. Observations: 4563 AIC: 1.006e+05
Df Residuals: 4552 BIC: 1.006e+05
Df Model: 10
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -1.064e+06 1.05e+05 -10.147 0.000 -1.27e+06 -8.58e+05
Kraftstoff[T.Benzin] 3.7212 3280.935 0.001 0.999 -6428.503 6435.946
Kraftstoff[T.Diesel] 5884.9259 3320.824 1.772 0.076 -625.501 1.24e+04
Kraftstoff[T.Elektro] 2.526e+04 4316.363 5.852 0.000 1.68e+04 3.37e+04
Kraftstoff[T.Elektro/Benzin] 6452.4374 3805.203 1.696 0.090 -1007.607 1.39e+04
Kraftstoff[T.Elektro/Diesel] 8751.1688 4639.732 1.886 0.059 -344.957 1.78e+04
Kraftstoff[T.Ethanol] -242.3806 1.51e+04 -0.016 0.987 -2.99e+04 2.94e+04
PS 137.6600 3.424 40.207 0.000 130.948 144.372
km -0.0868 0.004 -23.851 0.000 -0.094 -0.080
Erstzulassung 527.4547 51.681 10.206 0.000 426.134 628.775
Verbrauch_l_pro_100km 853.4036 163.158 5.231 0.000 533.535 1173.272
Omnibus: 4433.169 Durbin-Watson: 1.803
Prob(Omnibus): 0.000 Jarque-Bera (JB): 424743.861
Skew: 4.451 Prob(JB): 0.00
Kurtosis: 49.419 Cond. No. 6.55e+07


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 6.55e+07. This might indicate that there are
strong multicollinearity or other numerical problems.

Das Modell hat einen R-Squared von 63,1% und einen Adjusted R-Squared von 63,0% und schneidet somit mittelmäßig ab.
R-squared ist eine Kennzahl zur Beurteilung der Anpassungsgüte eines Modells und nimmt einen Wert zwischen 0% und 100% an. Als Bezugsbasis wird der Durchschnitt verwendet.
In statistischen Methoden wird meist lieber der adjusted R-squared genutzt. Da dieser die degrees of freedom miteinbezieht, lässt sich durch adj. R-squared einen besseren Rückschluss auf die Gesamtpopulation der Datensätze ziehen. Dieser ist meistens etwas schlechter als R-squared.
F-statistics ist die Menge der systematischen Varianz (MSM) geteilt durch die Menge der unsystematischen Varianz (MSR). Die Kennzahl gibt an, in welcher Höhe das Modell die Ergebnisausgabe der abhängigen Variable verbessert hat, verglichen zu Ungenauigkeit im Modell. Je höher F-statistics, desto besser. Mit einem Wert von 781 ist F-Statistics schneidet auch diese Kennzahl mittelmäßig ab.
Verbesserungspotenzial gibt es bei den Featuren:
Die p-Values einer Kraftstoff Kategorien liegen über 0,05% und haben daher statistisch gesehen keinen Einfluss auf den Preis. Ein sinnvoller Schritt wäre hier das Zusammenfassen einiger Kraftstoffarten. Ethanol als Kraftstoff kommt in diesem Datensatz nur einmal vor und sollte für das Modell entfernt werden. Elektro/Diesel und Elektro/Benzin könnten beispielsweise in der Kategorie Hypride zusammengefasst werden. Anschließend kann das Modell nochmal trainiert werden und weitere Verbesserungspotentiale identifiziert werden.

Dies ist jedoch nicht mehr Fokus des Projekts. Diese lineare Regression dient lediglich als Ausblick, was mit diesem Datensatz noch alles möglich gewesen wäre.

Fazit

Ziel dieses Projektes war es, die Daten von Gebrauchtfahrzeugangeboten aus Autoscout24 abzuziehen und zu analysieren. Dabei stand die Fragestellung im Vordergrund, welche Eigenschaften sich am stärksten auf den Verkaufspreis auswirken. Außerdem wurden die Daten auf Unterschiede zwischen den angebotenen Automarken untersucht.
Es wurde festegestellt, dass vor allem die PS, der Kilometerstand und das Jahr der Erstzulassung die größte Auswirkung auf den Verkaufspreis haben.

Mit der hier entwickelten Methode zur Datenaufbereitung und Modellierung könnten tatsächliche Anwendungen erschaffen werden, welche beispielsweise interessierten Käufern oder Verkäufern realistische Preisvorschläge auf Basis von Angaben zum Fahrzeugszustand machen (Mit Regression). Oder es könnte ein Klassifikationsalgorithmus bedient werden, welcher Angebote als guter oder schlechter Preis klassifiziert (wie auch heute schon bei Autoscout24 eingesetzt).

Das gesetzte Ziel wurde somit erreicht.